/*
Modified by SETH SIMON, Originally written by SETH SIMON
Last modification: FRI 9/26 3:30 AM

TODO:
STRCPYSE CRASHES THE PROGRAM WITH A SEGMENTATION FAULT AND DOESN'T WORK AT ALL
ONCE THAT'S FIXED, THE TOKENIZE FUNCTION (which depends on the strcpyse function) SHOULD BE WORKING


THE CASE WHERE ARRAYLENGTH(TOKENS) == 1 IS NOT HANDLED (EASY CASE, NO <>|), SO JUST FORK AND EXECV
THE CASE WHERE ARRAYLENGTH(TOKENS) > 3 IS NOT HANDLED
	this case has 4 subcases: 	a<b....>c
								a<b...|c
								a|b...>c
								a|b....|c
								
SEE TOKENIZE FUNCTION (LINE 400) FOR THE TOKEN-SPLITTING RULES
 */


#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>

enum boolean {FALSE,TRUE};

int arrayLength(char **); //how many elements in the array?
void tokenize(char *,char *[]); //custom token-splitting function, used for redirect & pipeline parsing
char *trimWhiteSpace(char *); //trims whitespace from beginning & end of a string
int indexOf(char *,char**); //finds the first index of a particular string in an array, -1 if not present
int needsPathVar(char *);  //true if a path doesn't start with . or /
void getArguments(char *,char *[]); //returns everything after the first space as an array of strings
char *getProgName(char *); //returns everything before the first space
char *getPath(char *,char **); //uses needsPathVar() and $PATH to return the string used by execv(),freopen(),etc.
int syntaxCorrect(char **); //true if no redir./pipe errors in the command, arg is tokens
void purgeEmptyElements(char *[]); //get rid of elements in a string array that are ""; shift displaced elements to the beginning of the array
void strcpyse(char *,char *,int,int); //(string copy start end) copies *(arg2+arg3) through *(arg2+arg4) to arg1, puts a \0 at the end
void emptyArray(char *[]); //change all elements in a string array to address 0

int main(int argc, char **argv) {
	char *path = "/bin:/usr/bin:/usr/local/bin"; //the path string variable
	char *pathArray[100]; //array of strings, holding each part of the path variable
	emptyArray(pathArray);
	//put defaults paths into the array:
	pathArray[0] = "/bin";
	pathArray[1] = "/usr/bin";
	pathArray[2] = "/usr/local/bin";
	
	char input[4096]; //holds the user's input line	
	while(TRUE) {
		char buf[4096];
		printf("%s: ",getcwd(buf,4096));
		gets(input);
		if(strlen(input)==0) {// \n is the only character
			continue;
		}
		char *tokens[500]; //tokens is an array of strings
		emptyArray(tokens);
		printf("BEFORE TOKENIZE\n");
		tokenize(input,tokens);
		printf("AFTER TOKENIZE\n");
		//trim whitespace from the beginning&end of each string in the tokens array:
		for(int i=0;i<arrayLength(tokens);i++) {
			tokens[i] = trimWhiteSpace(tokens[i]);
		}
		purgeEmptyElements(tokens);
		if(arrayLength(tokens)==0) { //the user typed only white space				
			continue;
		}

		
		//handle built-in commands (exit,cd,path):
		//PATH:
		if(strcmp(tokens[0],"path")==0) {
			if(arrayLength(tokens)>1) {
				printf("myshell: path: Redirections and pipelines not supported for built-in commands.\n");
				continue; //get next input line
			}
			else {
				printf("%s",path);
			}
		}
		//PATH +:
		else if(strncmp(tokens[0],"path+",5)==0) {
			if(arrayLength(tokens)>1) {
				printf("myshell: path: Redirections and pipelines not supported for built-in commands.\n");
				continue; //get next input line
			}
			else {
				//get argument of path command:
				char *argument;
				char *ptr = argument;
				char *ptr2 = tokens[0]+5;
				while(*ptr++ = *ptr2++)
					;
				//make sure the argument is a valid directory:
				DIR *dirptr;
				if(dirptr = opendir(argument))
					closedir(dirptr);
				else {
					printf("myshell: path: Invalid directory \"%s\"\n",argument);
					continue;
				}
				//append to path variable and array:
				strcat(path,":");
				strcat(path,argument);
				pathArray[arrayLength(pathArray)] = argument;
			}
		}
		//PATH -:
		else if(strncmp(tokens[0],"path-",5)==0) {
			if(arrayLength(tokens) > 1) {
				printf("myshell: path: Redirections and pipelines not supported for built-in commands.\n");
				continue;
			}
			else {
				//get argument of path command:
				char *argument;
				char *ptr = argument;
				char *ptr2 = tokens[0]+5;
				while(*ptr++ = *ptr2++)
					;
				//remove argument from path if argument is in the path variable:
				if(indexOf(argument,pathArray)!=-1) {
					//change array:
					for(int i=indexOf(argument,pathArray);i<arrayLength(pathArray);i++) {
						strcpy(pathArray[i],pathArray[i+1]);
					}
					//change string:
					strcpy(path,"");
					for(int i=0;i<arrayLength(pathArray);i++)
						strcat(path,pathArray[i]);
				}
				else {
					printf("myshell: path: Invalid directory \"%s\"\n",argument);
					continue;
				}
			}
		}
		//EXIT:
		else if(strcmp(tokens[0],"exit")==0) {
			if(arrayLength(tokens) > 1) {
				printf("myshell: exit: Redirection and pipelines not supported for built-in commands.\n");
				continue;
			}
			else {
				exit(0);
			}
		}
		//CD:
		else if(strncmp(tokens[0],"cd",2)==0) {
			if(arrayLength(tokens)>1) {
				printf("myshell: cd: Redirection and pipelines not supported for built-in commands.\n");
				continue;
			}
			else {
				//get argument:
				char *argument;
				char *ptr;
				char *ptr2;
				ptr=argument;
				ptr2=(tokens[0]);
				while(*ptr) {
					*ptr = *ptr2;
					ptr++;
					ptr2++;
				}
				if(chdir(argument)==-1) {
					printf("myshell: cd: chdir() failed.\n");
					continue;
				}
			}
		}
		//end of handling built-in commands
		//check redir. & pipeline syntax:
		if(!syntaxCorrect(tokens)) {
			printf("myshell: Syntax error in redirection/piping\n");
			continue;
		}
		if(arrayLength(tokens)==0) //then the user typed only white space
			continue;
		else if(arrayLength(tokens)==1) { //TODO!!!!!!!
			printf("TODO IF ARRAYLENGTH TOKENS == 1\n");
		}
		else if(arrayLength(tokens)==3) { //ls>a sort<a ls|sort
			//find specifcally what the (output) file is:
			if(strcmp(tokens[1],"<")==0) { //tokens[2] is file, tokens[0] is exe
				//confirm read access to file tokens[2]:
				char *str;
				FILE *fd;
				if(!(fd = freopen(tokens[2],"r",0))) {
					if(fd = fopen(tokens[2],"r"))  {//then the OS denied access to the file 1 line above
						printf("myshell: access to file \"%s\" denied.\n",tokens[2]);
						continue;
					}
					else { //the file doesn't exist, so creat it
						creat(tokens[2],0640); // rw- r-- ---
						fd = freopen(tokens[2],"r",0);
					}
				}
				//now that we have the fd assigned to standard input,
				//run the program including the options specified by the user
				char *progName;
				progName = getProgName(tokens[0]);	//e.g. "ls" "sort"
				char *path;
				path = getPath(progName,pathArray); //e.g. "/bin/ls" "../sh_script"
				char *arguments[500];
				emptyArray(arguments);
				getArguments(tokens[0],arguments);
				//	3rd: now we can do an exec
				int ppid = getpid(); //parent process id
				int pid = fork(); //current process' id
				if(pid==ppid) { //the parent is running, wait for child process to exit
					int status;
					wait(&status);
				}
				else { //the child is running, do the exec sys call
					execv(path,arguments);
				}
				continue;
			} //end if tokens[1] is <
			else if(strcmp(tokens[1],">")==0) {
				//open/create file
				FILE *fd;
				if(fd=fopen(tokens[2],"w"))
					;
				else {
					printf("myshell: unable to open/create file \"%s\"\n",tokens[2]);
					continue;
				}
				//get info:
				char *progName;
				progName = getProgName(tokens[0]);
				char *path;
				path = getPath(progName,pathArray);
				char *arguments[500];
				emptyArray(arguments);
				getArguments(tokens[0],arguments);
				fd = freopen(tokens[2],"w",stdin); //changes stdout
				//fork and exec:
				int ppid = getpid(); //saves parent process id
				fork();
				if(getpid()==ppid) {
					int status;
					wait(&status);
				}
				else {
					execv(path,arguments);
				}
				continue;
			}//end if tokens[1] is >
			else if(strcmp(tokens[1],"|")==0) {
				//get paths and arguments of each program:
				char *path1;
				char *path2;
				path1 = getPath(getProgName(tokens[0]),pathArray);
				path2 = getPath(getProgName(tokens[2]),pathArray);
				char *arguments1[500];
				char *arguments2[500];
				getArguments(tokens[0],arguments1);
				getArguments(tokens[2],arguments2);
				//do the piping process:
				int fdpipe[2];
				pipe(fdpipe);
				int ppid = getpid();
				fork();
				if(getpid()==ppid) { //parent, create a child to writer baby
					close(fdpipe[0]); //close reading end
					dup2(fdpipe[1],1); //duplicates writing end to stdout
					close(fdpipe[1]); //close original writing end
					if(fork()!=0) { //parent
						int status1,status2;
						wait(&status1); //wait for child just created to terminate
						wait(&status2); //wait for other child (reader) to terminate
						continue; //get next input from the user
					}
					else { //child, writes
						execv(path1,arguments1);
					}
				}
				else { //this is the reader child
					close(fdpipe[1]); //close writing end
					dup2(fdpipe[0],0); //duplicates reading end to stdin
					close(fdpipe[0]); //close original reading end
					execv(path2,arguments2);
				}
			} //end if tokens[1] is |
		} //end if arrayLength(tokens) is 3
		else { //if tokens > 3 elements
			printf("Sorry, didn't get this far yet\n");
			//TODO: what do I do??!???
		}
	} //end while(1)
	return 0;
} //end main
				
//USER-DEFINED FUNCTIONS:

//TRIMWHITESPACE: given a string, it returns a pointer to a string without any whitespace at the beginning or end
char *trimWhiteSpace(char * str) {
	if(strlen(str)==0)
		return str;
	char *ptr1;
	ptr1 = str;
	int firstIndex = 0; //*(str+firstIndex) points to first non-whitespace char in the string str
	//get first non-whitespace index of the string:
	while(*ptr1) { // \013 is vertical tab, \f is formfeed \r is carriage return
		if(*ptr1 == '\n' || *ptr1 == ' ' || *ptr1 == '\t' || *ptr1=='\013' || *ptr1 == '\f' || *ptr1 == '\r') {
			firstIndex++;
			ptr1++;
		}
		else if(!(*(ptr1+1))) { //then the string consists of only whitespace
			return "";
		}
		else
			break;
	} //end while
	//get last non-whitespace index of the string:
	*ptr1 = *(str+strlen(str)-1);
	int lastIndex = strlen(str)-1; //*(str+lastIndex) points to last non-whitespace char in the string str
	while(*ptr1 == '\n' || *ptr1 == ' ' || *ptr1 == '\t' || *ptr1 == '\013' || *ptr1 =='\f' || *ptr1 == '\r') {
		ptr1--;
		lastIndex--;
	}
	//use firstIndex & lastIndex to create the result:
	char *result;
	strcpyse(result,str,firstIndex,lastIndex);
	return result;
}

//ARRAYLENGTH: given an array of pointers, returns the index of the first pointer that points to address 0 (said index is the same as the arrayLength)
int arrayLength( char** array) {
	int result = 0;
	char *ptr; //used to traverse through the array
	int i=0;
	for(ptr=array[0];ptr!=0 && ptr;i++,result++,ptr=array[i])
		;
	return result;
}

//NEEDSPATHVAR: returns true if the shell needs to use the path variable to find the location of a program. true if it doesn't start with a / or .
int needsPathVar(char * str) {
	if(*str=='.' || *str=='/')
		return FALSE;
	else
		return TRUE;
}	


//GETARGUMENTS: returns an array of strings, of everything after the first space, where array elements are separated by spaces in the argument to this function
void getArguments(char *str,char *arguments[]) {
	char *start; //points to first character after the first space
	start = str;
	while(*start++ != ' ')
		;
	if(strlen(start)==0) //there are no arguments
		return;
	else {//we need to tokenize based on spaces, place into result array, and return it
		//tokenize:
		for(int i=0;arguments[i]=strtok(start," ");i++)
			;
		return;
	}
}

//GETPATH: returns a string that is a valid argument for functions such as freopen(), excv(), etc.
char *getPath(char *str,char *path[]) {
	if(!needsPathVar(str))
		return str;
	else if(arrayLength(path)==0)
		return "";
	else {
		char *result;
		for(int i=0;i<arrayLength(path);i++) {
			result = strcpy(result,path[i]);
			if(*(strrchr(path[i],'/')+1)) //last character is not /, append /
				result=strcat(result,"/");
			else //last character is /, do nothing
				;
			result=strcat(result,str);
			//see if result exists, if it doesn't exist, go through loop again with next portion 
			//of path var
			FILE *fp;
			if(fp = fopen(result,"r")) { //file exists
				fclose(fp);
				return result;
			}
		}
		return ""; //if the function reaches this line, file not found
	}
}


//TOKENIZE: split into tokens based on these rules:
/*
first token is characters (e.g. "ls --colour=auto ")
next token is "<" ">" or "|"
next token is characters
next token is "<" ">" "|"
etc...
*/
void tokenize(char *str,char *tokens[]) {
	printf("START TOKENIZE\n");
	int nextResultIndex = 0; //next index in the tokens array that's free
	printf("DECLARED AN INT\n");
	char *ptr; //used to traverse str
	printf("DECLARED *PTR\n");
	ptr = str;
	printf("PTR=STR\n");
	int firstIndex = 0; //*(str+firstIndex) points to first character that
	int lastIndex = 0; //*(str+lastIndex) points to last that needs put into array
	printf("ONE LINE BEFORE WHILE LOOP\n");
	while(*ptr) {
		if(*ptr=='>' || *ptr=='<' || *ptr=='|') {
			printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
			//put all the preceding characters into result array
			strcpyse(tokens[nextResultIndex],str,firstIndex,lastIndex);
			nextResultIndex++;
			//put < > | into result array
			strcpyse(tokens[nextResultIndex],str,lastIndex+1,lastIndex+1);
			nextResultIndex++;
			*ptr++;
			firstIndex = lastIndex + 2; //update to the char after the < > |
			lastIndex = firstIndex;
		}
		else { //*ptr is not < > or |
			printf("AAAAAAAAAAAAAAAAAAAAAAAAAAA\n");
			ptr++;
			if(!*ptr) {//if the next char is \0
				//we need to put any previously read characters into the result array
				//previously read characters are from firstIndex to *(ptr) null or *(ptr-1)last char
				printf("BEF\n");
				strcpyse(tokens[nextResultIndex],str,firstIndex,lastIndex);
				printf("AFT\n");
			}
			lastIndex++;
		}
	} //end while
	printf("H\n");
	printf("%s\n",tokens[0]);
	printf("I\n");
}

//GETPROGNAME: returns everything before the first space
char *getProgName(char * str) {
	//find index of first space:
	char *ptr;
	char *result;
	if(ptr = strchr(str,' ')) { //if there's a space; ptr points to it
		strcpyse(result,str,0,ptr-1-str);
		return result;
	}
	else { //no spaces in str
		return str;
	}
}


//INDEXOF: returns the index of a string in a string array, or -1 if it's not present
int indexOf(char *str,char** array) {
	if(arrayLength(array)==0)
		return -1;
	for(int i=0;i<arrayLength(array);i++) {
		if(strcmp(array[i],str)==0)
			return i;
	}
	return -1;
}


//SYNTAXCORRECT: returns true if redir. & pipelining syntax is correct
int syntaxCorrect(char **tokens) {
	//things to check:
	//   1.)	tokens.length must be odd
	//   2.)	tokens[even] must be characters
	//   3.)	tokens[odd] must be < > or |
	//   4.)	second token must be < or | (unless tokens.length is 3)
	//   5.)	second to last token must be > or | (unless tokens.length is 3)
	
	
	//preliminary housekeeping:
	if(arrayLength(tokens)==0)
		return TRUE;
	if(arrayLength(tokens)==1) {
		if(strcmp(tokens[0],"<")!=0 && strcmp(tokens[0],">")!=0 && strcmp(tokens[0],"|")!=0)
			return TRUE;
		else
			return FALSE;
	}
	// Condition 1:
	if(arrayLength(tokens)%2==0)
		return FALSE;
	// 2 and 3:
	for(int i=0;i<arrayLength(tokens);i++) {
		if(i%2==0) { //even, tokens[i] must be characters
			if(strchr(tokens[i],'<') || strchr(tokens[i],'>') || strchr(tokens[i],'|'))
				return FALSE;
		}
		else { //odd, tokens[i] must be < > |
			if(strcmp(tokens[i],"<")!=0 && strcmp(tokens[i],">")!=0 && strcmp(tokens[i],"|")!=0)
				return FALSE;
		}
	}
	 // deal with the (unless tokens.length is 3) restrictions:
	 if(arrayLength(tokens)==3)
		return TRUE;
	//  Condition 4:
	if(strcmp(tokens[1],">")==0)
		return FALSE;
	//Condition 5:
	if(strcmp(tokens[arrayLength(tokens)-2],"<")==0)
		return FALSE;
	return TRUE;
}
		

//PURGEEMPTYELEMENTS: any elements that are "" are removed.
//so an array that looks like {"ls",">","","sort","","|","defaultprinter -d"}
//becomes: {"ls",">","sort","|","defaultprinter -d",0,0} 0 is address 0
void purgeEmptyElements(char *tokens[]) {
	char *result[500];
	emptyArray(result);
	if(arrayLength(tokens)==0)
		return;
	int count = 0;
	for(int i=0;i<arrayLength(tokens);i++) {
		if(strlen(tokens[i])==0) {
			continue;
			count++;
		}
		else { //add this non-empty element to the result array
			result[i] = strcpy(result[i],tokens[i]);
		}
	}
	//copy result array to tokens array, make remainder of tokens array 0:
	for(int i=0;i<arrayLength(result);i++) {
		strcpy(tokens[i],result[i]);
	}
	for(int i=arrayLength(result);i<500;i++) {
		tokens[i]=0;
	}
}


//STRCPYSE: (string copy start end) copies char index #start through char #end (inclusive) to destination
// and adds a \0
void strcpyse(char *dest,char *src,int start,int end) {
	int numCopied = 0;
	src = src + start;
	printf("HELLO\n");
	while(numCopied <= end-start) {
		printf("%d\n",numCopied);
		*dest = *src;
		numCopied++;
		src++;
		d++;
	}
	//add \0:
	dest++;
	*dest = '\0';
	printf("DINE\n");
}


//EMPTYARRAY: change all elements to 0
void emptyArray(char *array[]) {
	for(int i=0;i<500;i++) {
		array[i]=0;
	}
}
